package com.sd.repay.service.impl;

import com.sd.repay.common.CommonResult;
import com.sd.repay.common.Constants;
import com.sd.repay.common.Page;
import com.sd.repay.dao.BaseDao;
import com.sd.repay.pojo.RepayPlan;
import com.sd.repay.pojo.RepayPlanDetail;
import com.sd.repay.service.RepayPlanDetailService;
import com.sd.repay.service.RepayPlanService;
import com.sd.repay.utils.DateUtil;
import com.sd.repay.utils.JsonUtil;
import com.sd.repay.utils.WebUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author sd
 * @Description: 还款计划Service接口实现类
 * @date 2017年10月25日
 */
@Service
public class RepayPlanServiceImpl implements RepayPlanService {

    private static Logger log = LoggerFactory.getLogger(RepayPlanServiceImpl.class);

    @Autowired
    private BaseDao baseDao;
    @Autowired
    private RepayPlanDetailService planDetailService;


    /**
     * 生成还款计划
     *
     * @param map
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public List<Map<String, String>> generateRepayPlan(String repayType, Map<String, String> map, List<Map<String, String>> detailMaps) throws Exception {

        String merchantNo = map.get("merchantNo");
        String cardNo = map.get("cardNo");
        String batchNo = map.get("batchNo");

        String sql = " INSERT INTO t_repay_plan(merchant_no, batch_no, card_no, repay_amount, repay_begin_time, repay_end_time, " +
                " ensure_amount, repay_num, mer_fee_rate, mer_single_fee, agent_fee_rate, agent_single_fee, repay_fee, " +
                " acq_code, acq_merchant_no, repay_type, status, repay_desc, create_time) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        List<Object> params = new ArrayList<>();
        params.add(map.get("merchantNo"));
        params.add(map.get("batchNo"));
        params.add(map.get("cardNo"));
        params.add(map.get("repayAmount"));
        params.add(map.get("repayBeginTime"));
        params.add(map.get("repayEndTime"));
        params.add(map.get("ensureAmount"));
        params.add(map.get("repayNum"));
        params.add(map.get("merFeeRate"));
        params.add(map.get("merSingleFee"));
        params.add(map.get("agentFeeRate"));
        params.add(map.get("agentSingleFee"));
        params.add(map.get("repayFee"));
        params.add(map.get("acqCode"));
        params.add(map.get("acqMerchantNo"));
        params.add(map.get("repayType"));
        params.add(Constants.PLAN_STATUS_INIT);
        params.add(map.get("repayDesc"));
        params.add(new Date());
        int saveCount = baseDao.execute(sql, params.toArray());

        log.info(merchantNo + "/" + cardNo + "新增还款计划影响的行数：" + saveCount + "批次号：" + batchNo);
        if (saveCount > 0) {
            //插入明细
            sql = " INSERT INTO t_repay_plan_detail(merchant_no, card_no, batch_no, plan_no, plan_type, plan_time, plan_index, plan_amount, repay_type, " +
                    " acq_code, acq_merchant_no, create_time) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
            List<Object[]> paramsList = new ArrayList<>();
            List<Object> paramsObj = null;
            String planNo = "";
            for (Map<String, String> planDetailMap : detailMaps) {

                planNo = WebUtil.nextUniqueSeqNo("t_repay_plan_detail", "", 20);

                paramsObj = new ArrayList<>();

                paramsObj.add(map.get("merchantNo"));
                paramsObj.add(map.get("cardNo"));
                paramsObj.add(map.get("batchNo"));
                paramsObj.add(planNo);
                paramsObj.add(planDetailMap.get("planType"));
                paramsObj.add(planDetailMap.get("planTime"));
                paramsObj.add(planDetailMap.get("planIndex"));
                paramsObj.add(planDetailMap.get("planAmount"));
                paramsObj.add(map.get("repayType"));
                paramsObj.add(map.get("acqCode"));
                paramsObj.add(map.get("acqMerchantNo"));
                paramsObj.add(new Date());

                paramsList.add(paramsObj.toArray());

                planDetailMap.put("planNo", planNo);
            }
            int[] result = baseDao.batchExecute(sql, paramsList);
            log.info(merchantNo + "/" + cardNo + "/" + batchNo + "新增还款计划明细影响的行数：" + result.length + "redis缓存明细行数：" + detailMaps.size());
            if (result.length != paramsList.size()) {
                throw new RuntimeException("批量新增还款明细记录数异常，请核查数据后操作");
            }
            return detailMaps;
        }
        return null;
    }

    /**
     * 根据批次号获取唯一还款计划
     *
     * @param batchNo
     * @return
     * @throws Exception
     */
    @Override
    public RepayPlan getRepayPlanByBatchNo(String batchNo) throws Exception {
        String sql = " SELECT * FROM t_repay_plan WHERE batch_no = ? ";
        return baseDao.findFirst(RepayPlan.class, sql, batchNo);
    }

    /**
     * 根据批次号，状态获取唯一还款计划
     *
     * @param batchNo
     * @param status
     * @return
     * @throws Exception
     */
    @Override
    public RepayPlan getRepayPlanByBatchNoAndStatus(String batchNo, String status) throws Exception {
        String sql = " SELECT * FROM t_repay_plan WHERE batch_no = ? AND status = ? ";
        return baseDao.findFirst(RepayPlan.class, sql, new Object[]{batchNo, status});
    }

    /**
     * 获取需要进行还款的所有计划
     *
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public List<RepayPlan> getAllToExecutePlans() throws Exception {

        String uuid = WebUtil.genUUID();
        String nowTime = DateUtil.getDate("yyyy-MM-dd HH:mm:ss");
        String sql = " UPDATE t_repay_plan SET row_lock = ? WHERE status IN (?, ?) AND repay_begin_time < ?";
        int count = baseDao.execute(sql, new Object[]{uuid, Constants.PLAN_STATUS_UN_EXECUTE, Constants.PLAN_STATUS_IN_EXECUTE, nowTime});
        if (count > 0) {
            sql = "SELECT * FROM t_repay_plan WHERE row_lock = ? AND status IN (?, ?) AND repay_begin_time < ?";
            return baseDao.findList(RepayPlan.class, sql, new Object[]{uuid, Constants.PLAN_STATUS_UN_EXECUTE, Constants.PLAN_STATUS_IN_EXECUTE, nowTime});
        }
        return new ArrayList<>();
    }

    /**
     * 修改计划状态
     *
     * @param batchNo
     * @param newStatus
     * @param oldStatus
     * @param remark
     * @return
     * @throws Exception
     */
    @Override
    public int updateRepayPlanStatus(String batchNo, String newStatus, String oldStatus, String remark) throws Exception {

        String sql = " UPDATE t_repay_plan SET status = ?, remark = ? ";
        List<Object> params = new ArrayList<>();
        params.add(newStatus);
        params.add(remark);

        sql += "WHERE batch_no = ? ";
        params.add(batchNo);

        if (StringUtils.isNotBlank(oldStatus)) {
            sql += " AND status = ? ";
            params.add(oldStatus);
        }

        if (StringUtils.isBlank(oldStatus) && Constants.PLAN_STATUS_FAIL.equals(newStatus)) {
            sql += " AND status IN ('0', '1', '2', '7')";
        }
        return baseDao.execute(sql, params.toArray());
    }

    /**
     * 根据商户号获取所有还款计划
     *
     * @param merchantNo
     * @return
     * @throws Exception
     */
    @Override
    public List<RepayPlan> getRepayPlanByMerchantNo(String merchantNo) throws Exception {

        String sql = " SELECT * FROM t_repay_plan WHERE merchant_no = ? ";
        return baseDao.findList(RepayPlan.class, sql, merchantNo);
    }

    /**
     * 根据卡牌编号获取所有还款计划
     *
     * @param cardNo
     * @return
     * @throws Exception
     */
    @Override
    public List<RepayPlan> getRepayPlanByCardNo(String cardNo) throws Exception {

        String sql = " SELECT * FROM t_repay_plan WHERE card_no = ? ";
        return baseDao.findList(RepayPlan.class, sql, cardNo);
    }

    /**
     * 根据卡牌编号获取所有还款中的还款计划
     *
     * @param cardNo
     * @return
     * @throws Exception
     */
    @Override
    public List<RepayPlan> getInExecutePlanByCardNo(String cardNo) throws Exception {

        String sql = " SELECT * FROM t_repay_plan WHERE card_no = ? AND status IN (?,?,?) ORDER BY id DESC ";
        return baseDao.findList(RepayPlan.class, sql, new Object[]{cardNo, Constants.PLAN_STATUS_UN_EXECUTE, Constants.PLAN_STATUS_IN_EXECUTE, Constants.PLAN_STATUS_HANG});
    }

    /**
     * 终止还款计划
     *
     * @param batchNo
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public CommonResult stopRepayPlan(String batchNo) throws Exception {

        RepayPlan repayPlan = getRepayPlanByBatchNo(batchNo);
        if (null == repayPlan) {
            return CommonResult.build(403, "终止的计划不存在");
        }
        String merchantNo = repayPlan.getMerchantNo();
        //记录日志信息
        WebUtil.saveOperateLog(merchantNo, "stopRepayPlan", "t_repay_plan", "", "", "批次对应的商户，终止了此还款计划");

        //先查询当前批次下面是否有执行中的明细编号，如果有则改状态为预处理状态
        List<RepayPlanDetail> details = planDetailService.getDetailByBatchNoAndStatus(batchNo, Constants.PLAN_DETAIL_STATUS_IN_EXECUTE);
        log.info("还款批次号[{}]还在执行中的还款明细记录条数[{}]", new Object[]{batchNo, details.size()});

        int count = 0;
        if (!CollectionUtils.isEmpty(details)) {

            count = updatePlanExpectStatus(batchNo, Constants.PLAN_EXPECT_STATUS_STOP, "用户终止");
            log.info("还款计划批次号[{}]预处理终止影响的行数[{}]", new Object[]{batchNo, count});
            return CommonResult.ok();
        }

        //下一笔执行的明细必须是消费，跟用户平账，否则预终止
        RepayPlanDetail nextDetail = planDetailService.getNextExeuteDetailByBatchNo(batchNo);
        if (!Constants.PLAN_DETAIL_TYPE_OUT.equals(nextDetail.getPlanType())) {

            count = updatePlanExpectStatus(batchNo, Constants.PLAN_EXPECT_STATUS_STOP, "用户终止");
            log.info("还款计划批次号[{}]预处理终止影响的行数[{}]", new Object[]{batchNo, count});
            return CommonResult.ok();
        }

        count = updateRepayPlanStatus(batchNo, Constants.PLAN_STATUS_STOP, Constants.PLAN_STATUS_IN_EXECUTE, "用户终止");
        log.info("还款计划批次号[{}]终止影响的行数[{}]", new Object[]{batchNo, count});

        return CommonResult.ok();
    }

    /**
     * 修改计划预处理状态
     *
     * @param batchNo
     * @param expectStatus
     * @param remark
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public int updatePlanExpectStatus(String batchNo, String expectStatus, String remark) throws Exception {

        String sql = "UPDATE t_repay_plan SET expect_status = ?, remark = ? WHERE batch_no = ?";
        return baseDao.execute(sql, new Object[]{expectStatus, remark, batchNo});
    }

    /**
     * 查询计划
     *
     * @param merchantNo 商户号
     * @param status     0:未执行 1：已执行 空：全部
     * @param startTime  yyyy-MM-dd HH:mm:ss
     * @param endTime    yyyy-MM-dd HH:mm:ss
     * @param pageNo     页码
     * @param pageSize   每页记录数
     * @return
     * @throws Exception
     */
    @Override
    public Page<Map<String, Object>> getRepayPlanPage(String merchantNo, String status, String startTime, String endTime, int pageNo, int pageSize) throws Exception {
        String sql = " SELECT p.batch_no planNo, p.card_no cardNo, m.settle_bank_no settleBankNo, p.repay_amount totalAmount, " +
                " p.repay_num totalNum, p.success_repay_num successNum, p.status, p.repay_fee repayFee, " +
                " m.account_no accountNo, CONCAT(DATE_FORMAT(p.repay_begin_time, '%Y-%m-%d'), '~', DATE_FORMAT(p.repay_end_time, '%Y-%m-%d')) repayTimeSection," +
                " DATE_FORMAT(p.create_time, '%Y-%m-%d %H:%i:%S') createTime FROM t_repay_plan p, t_card_manage m " +
                " WHERE p.merchant_no = ? and p.card_no = m.card_no and p.create_time between ? and ?  ";
        if ("0".equals(status)) {
            sql += " and p.status in ('0','1') ";
        } else if ("1".equals(status)) {
            sql += " and p.status in ('2','5','6','7') ";
        } else if ("2".equals(status)) {
            sql += " and p.status in ('3') ";
        } else if ("3".equals(status)) {
            sql += " and p.status in ('4') ";
        }
        return baseDao.queryPagination(pageNo, pageSize, sql, merchantNo, startTime, endTime);
    }

    /**
     * 获取终态的还款计划
     *
     * @return
     * @throws Exception
     */
    @Override
    public List<RepayPlan> getAllCompletePlan() throws Exception {
        String sql = " SELECT * FROM t_repay_plan WHERE status IN (?, ?, ?) AND (settle_status IS NULL OR settle_status = ? OR settle_status = '') ORDER BY create_time ASC";
        return baseDao.findList(RepayPlan.class, sql, new Object[]{Constants.PLAN_STATUS_SUCCESS, Constants.PLAN_STATUS_FAIL, Constants.PLAN_STATUS_STOP, Constants.PLAN_SETTLE_STATUS_UN_SETTLE});
    }

    /**
     * 还款计划清算
     *
     * @param repayPlan
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public int settlePlanInfo(RepayPlan repayPlan) throws Exception {

        String batchNo = repayPlan.getBatchNo();
        String status = repayPlan.getStatus();
        log.info("还款计划批次号[{}]到终态[{}]后开始进行清算", new Object[]{batchNo, status});

        BigDecimal merFeeRate = repayPlan.getMerFeeRate();
        BigDecimal merSingleFee = repayPlan.getMerSingleFee();

        BigDecimal allOutAmount = BigDecimal.ZERO;
        BigDecimal allInAmount = BigDecimal.ZERO;
        int successRepayNum = 0;
        BigDecimal actualPayFee = BigDecimal.ZERO;
        BigDecimal actualWithdrawFee = BigDecimal.ZERO;

        Map<String, Object> allOutCountInfo = planDetailService.countInfoByBatchAndTypeAndStatus(batchNo, Constants.PLAN_DETAIL_TYPE_OUT, Constants.PLAN_DETAIL_STATUS_SUCCESS);
        log.info("还款计划批次[{}]类型为[{}]状态为[{}]总的明细统计信息为[{}]", new Object[]{batchNo, Constants.PLAN_DETAIL_TYPE_OUT, Constants.PLAN_DETAIL_STATUS_SUCCESS, JsonUtil.objectToJson(allOutCountInfo)});
        try {
            allOutAmount = new BigDecimal(allOutCountInfo.get("sumAmount").toString());
        } catch (Exception e) {
            allOutAmount = BigDecimal.ZERO;
        }
        Map<String, Object> allInCountInfo = planDetailService.countInfoByBatchAndTypeAndStatus(batchNo, Constants.PLAN_DETAIL_TYPE_IN, Constants.PLAN_DETAIL_STATUS_SUCCESS);
        log.info("还款计划批次[{}]类型为[{}]状态为[{}]总的明细统计信息为[{}]", new Object[]{batchNo, Constants.PLAN_DETAIL_TYPE_IN, Constants.PLAN_DETAIL_STATUS_SUCCESS, JsonUtil.objectToJson(allInCountInfo)});
        try {
            allInAmount = new BigDecimal(allInCountInfo.get("sumAmount").toString());
        } catch (Exception e) {
            allOutAmount = BigDecimal.ZERO;
        }
        try {
            successRepayNum = Integer.parseInt(allInCountInfo.get("countNum").toString());
        } catch (Exception e) {
            successRepayNum = 0;
        }
        actualPayFee = allOutAmount.multiply(merFeeRate);
        actualWithdrawFee = new BigDecimal(successRepayNum).multiply(merSingleFee);

        StringBuilder sb = new StringBuilder();
        sb.append("还款计划批次号：" + batchNo);
        sb.append("，对应的商户交易费率：" + merFeeRate.toString());
        sb.append("，代付单笔成本：" + merSingleFee.toString());
        sb.append("，总共成功OUT金额：" + allOutAmount);
        sb.append("，总共成功IN金额：" + allInAmount);
        sb.append("，总共成功IN笔数：" + successRepayNum);
        sb.append("，实际交易手续费：" + actualPayFee.toString());
        sb.append("，实际代付手续费：" + actualWithdrawFee.toString());
        log.info(sb.toString());

        String sql = " UPDATE t_repay_plan SET success_repay_amount = ?, success_pay_amount = ?, " +
                " success_repay_num = ?, actual_pay_fee = ?, actual_withdraw_fee = ?, complete_time = ?, " +
                " settle_status = ? WHERE batch_no = ? ";
        List<Object> params = new ArrayList<>();
        params.add(allInAmount);
        params.add(allOutAmount);
        params.add(successRepayNum);
        params.add(actualPayFee);
        params.add(actualWithdrawFee);
        params.add(new Date());
        params.add(Constants.PLAN_SETTLE_STATUS_SETTLED);
        params.add(batchNo);

        return baseDao.execute(sql, params.toArray());
    }

    /**
     * 激活还款计划（挂起到还款中）
     *
     * @param batchNo
     * @param operator
     * @return
     * @throws Exception
     */
    @Override
    @Transactional
    public CommonResult wakeRepayPlan(String batchNo, String operator) throws Exception {

        RepayPlan repayPlan = getRepayPlanByBatchNo(batchNo);
        String repayStatus = repayPlan.getStatus();
        if (!Constants.PLAN_STATUS_HANG.equals(repayStatus)) {
            return CommonResult.build(403, "计划" + batchNo + "非挂起状态");
        }
        //查询导致此计划挂起的代付明细
        RepayPlanDetail planDetail = getLatestHangPlanDetail(batchNo);
        if (planDetail == null) {
            return CommonResult.build(403, "不存在导致挂起的计划明细");
        }
        //修改明细状态为还款中，并设置操作次数为0
        int count = planDetailService.resertDetailCount(planDetail.getPlanNo());
        log.info("还款批次号：" + batchNo + "导致挂起的明细编号：" + planDetail.getPlanNo() + "重置操作次数为0影响的行数：" + count);
        if (count > 0) {
            count = planDetailService.updateDetailStatus(planDetail.getPlanNo(), Constants.PLAN_DETAIL_STATUS_UN_EXECUTE, "", "重新激活计划重置挂起的明细状态");
            log.info("还款批次号：" + batchNo + "导致挂起的明细编号：" + planDetail.getPlanNo() + "状态为未执行影响的行数：" + count);
            if (count > 0) {
                count = updateRepayPlanStatus(batchNo, Constants.PLAN_STATUS_IN_EXECUTE, Constants.PLAN_STATUS_HANG, "重新激活");
                log.info("还款批次号：" + batchNo + "状态为还款中影响的行数：" + count);
                if (count < 1) {
                    throw new RuntimeException("还款计划激活失败");
                }
            }
        }
        //记录日志信息
        WebUtil.saveOperateLog(operator, "wakeRepayPlan", "t_repay_plan", "", "", "还款计划批次号" + batchNo + "重新激活");
        return CommonResult.ok();
    }

    /**
     * 获取最近的导致计划挂机的计划编号
     *
     * @return
     * @throws Exception
     */
    @Override
    public RepayPlanDetail getLatestHangPlanDetail(String batchNo) throws Exception {
        String sql = "SELECT * FROM t_repay_plan_detail WHERE batch_no = ? AND plan_count = ? AND plan_status = ? AND plan_type = ? ";
        return baseDao.findFirst(RepayPlanDetail.class, sql, new Object[]{batchNo, Constants.PLAN_MAX_EXECUTE_COUNT, Constants.PLAN_DETAIL_STATUS_FAIL, Constants.PLAN_DETAIL_TYPE_IN});
    }


}
